/*******************************************************************************
 * Copyright (c) 2011 LegSem.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl-3.0.txt
 * 
 * Contributors:
 *     LegSem - initial API and implementation
 ******************************************************************************/
package com.legsem.legstar.connector.it.jee6.web.tran;

import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;

import javax.annotation.Resource;
import javax.resource.ResourceException;
import javax.resource.cci.Connection;
import javax.resource.cci.ConnectionFactory;
import javax.resource.cci.LocalTransaction;
import javax.servlet.ServletException;
import javax.servlet.annotation.HttpConstraint;
import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
import javax.servlet.annotation.ServletSecurity;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.legsem.legstar.cci.LegStarInteraction;
import com.legsem.legstar.cci.LegStarSingleIOInteractionSpec;
import com.legsem.legstar.cci.LegStarSingleIOInteractionSpecImpl;
import com.legstar.host.invoke.model.HostProgram;
import com.legstar.test.coxb.lsfileae.Dfhcommarea;
import com.legstar.test.coxb.lsfileae.bind.DfhcommareaTransformers;

/**
 * This sample servlet uses the LegStar JCA Connector over sockets directly.
 * <p/>
 * This is Transactional servlet using component-managed transactions.
 * <p/>
 * Also showcases using security mapping (where an AS users group maps to a
 * mainframe User/Password). Only users belonging to group LegstarTesterRole are
 * allowed to use this servlet. They must be authenticated over HTTPS/TLS.
 * <p/>
 * Browser clients invoke this servlet using HTTP GET commands, with the
 * following query parameters:
 * <ul>
 * <li>oper: is the operation requested each corresponding to one of the EJB
 * methods: C for createClient, R for readClient, F for failCreateClient and D
 * for deleteClient</li>
 * <li>id: is the client id (for new clients the rest of the data is generated
 * programmatically)</li>
 * </ul>
 * There are 3 CICS programs involved, all using the same commarea structure:
 * <ul>
 * <li>LSFILEAW: Creates a client record</li>
 * <li>LSFILEAE: Retrieves client data using a client number</li>
 * <li>LSFILEAR: Removes a client record</li>
 * </ul>
 */
@WebServlet("/TranClientServlet")
@ServletSecurity(@HttpConstraint(transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed = { "LegstarTesterRole" }))
public class TranClientServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    /**
     * The JCA connector as known to JNDI.
     */
    @Resource(name = "legstar.socket.connector")
    private ConnectionFactory connectionFactory;

    /**
     * Dispatches the execution request depending on query parameters.
     */
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {

        String id = request.getParameter("id");
        String oper = request.getParameter("oper");

        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("<title> Results from servlet execution </title>");
        out.println("</head>");
        out.println("<body>");

        if (isValidRequest(id, oper, out)) {
            Connection connection = null;
            LocalTransaction tx = null;
            try {
                connection = connectionFactory.getConnection();
                tx = connection.getLocalTransaction();
                tx.begin();

                if (oper.equals("C")) {
                    createClient(connection, createDfhcommarea(id));
                } else if (oper.equals("R")) {
                    readClient(connection, readDfhcommarea(id));
                } else if (oper.equals("F")) {
                    failCreateClient(connection, createDfhcommarea(id));
                } else if (oper.equals("D")) {
                    deleteClient(connection, readDfhcommarea(id));
                }
                tx.commit();
                out.println("<h1>" + "Operation \"" + oper + "\" on client "
                        + id + " succeeded" + "</h1>");
            } catch (ResourceException e) {
                if (tx != null) {
                    try {
                        tx.rollback();
                    } catch (ResourceException e1) {
                        e1.printStackTrace();
                    }
                }
                throw new ServletException(e);
            } finally {
                if (connection != null) {
                    try {
                        connection.close();
                    } catch (ResourceException e1) {
                        e1.printStackTrace();
                    }
                }
            }
        }

        out.println("</body>");
        out.println("</html>");
        out.close();
    }

    /**
     * Check if this is a valid request.
     * 
     * @param id a Client ID
     * @param oper an operation to perform on that client
     * @param out the HTML response
     * @return true if the requerst is fine
     */
    private boolean isValidRequest(String id, String oper, PrintWriter out) {
        if (id == null) {
            out.println("You must specify an id parameter");
            return false;
        }
        if (!id.matches("\\d{1,6}")) {
            out.println("Id must contain only digits (1 to 6)");
            return false;
        }
        if (oper == null) {
            out.println("You must specify an oper parameter (values: C for CREATE, R for READ, F for FAILURE, D for DELETE");
            return false;
        }
        if (!"CRFD".contains(oper)) {
            out.println("Invalid operation"
                    + oper
                    + ". Valid values for oper: C for CREATE, R for READ, F for FAILURE, D for DELETE");
            return false;
        }
        return true;
    }

    /**
     * Creates Client data for a new client ID.
     * 
     * @param id the new client ID
     * @return the commarea filled with test data
     */
    private Dfhcommarea createDfhcommarea(String id) {
        Dfhcommarea commarea = new Dfhcommarea();
        commarea.setComNumber(Long.valueOf(id));
        commarea.setComAmount(new BigDecimal(Long.valueOf(id) * 3.14)
                .toString());
        commarea.setComDate("12111965");
        com.legstar.test.coxb.lsfileae.ComPersonal personal = new com.legstar.test.coxb.lsfileae.ComPersonal();
        personal.setComAddress(id + " street");
        personal.setComName("P" + id);
        personal.setComPhone("0123456789");
        commarea.setComPersonal(personal);
        return commarea;

    }

    /**
     * Client data for an existing client ID.
     * 
     * @param id the new client ID
     * @return the commarea filled with test data
     */
    private Dfhcommarea readDfhcommarea(String id) {
        Dfhcommarea commarea = new Dfhcommarea();
        commarea.setComNumber(Long.valueOf(id));
        return commarea;

    }

    /**
     * Creates a new client. If the client number already exist, an exception is
     * raised and the transaction is rolled back.
     * 
     * @param connection mainframe connection
     * @param commarea the client data
     * @return the input data
     * @throws ResourceException if client already exist
     */
    public Dfhcommarea createClient(Connection connection, Dfhcommarea commarea)
            throws ResourceException {
        return execute(connection, "LSFILEAW", commarea);
    }

    /**
     * Retrieve a client
     * 
     * @param connection mainframe connection
     * @param commarea the client data
     * @return the client data
     * @throws ResourceException if client does not exist
     */
    public Dfhcommarea readClient(Connection connection, Dfhcommarea commarea)
            throws ResourceException {
        return execute(connection, "LSFILEAE", commarea);
    }

    /**
     * Generates a failure during an update process.
     * <p/>
     * The first write succeeds but the second fails (because the client already
     * exists). Since both updates are part of the same transaction, roll back
     * should undo both creations.
     * 
     * @param connection mainframe connection
     * @param commarea the input client data
     * @return the input client data
     * @throws ResourceException when second write fails
     */
    public Dfhcommarea failCreateClient(Connection connection,
            Dfhcommarea commarea) throws ResourceException {
        execute(connection, "LSFILEAW", commarea);
        return execute(connection, "LSFILEAW", commarea);
    }

    /**
     * Remove a client.
     * 
     * @param connection mainframe connection
     * @param commarea the input client data
     * @return the input client data
     * @throws ResourceException
     */
    public Dfhcommarea deleteClient(Connection connection, Dfhcommarea commarea)
            throws ResourceException {
        return execute(connection, "LSFILEAR", commarea);
    }

    /**
     * Generic execution of a remote program.
     * <p/>
     * Mainframe programs reply by setting the client number to zero if
     * something went wrong.
     * 
     * @param programName the program name
     * @param commarea the input structure
     * @return the output structure
     * @throws ResourceException if execution fails
     */
    private Dfhcommarea execute(Connection connection, String programName,
            Dfhcommarea commarea) throws ResourceException {

        LegStarInteraction interaction = (LegStarInteraction) connection
                .createInteraction();

        commarea = (Dfhcommarea) interaction.execute(getISpec(programName),
                commarea);

        if (commarea.getComNumber() == 0) {
            throw new ResourceException("Remote execution failed");
        }

        return commarea;

    }

    /**
     * Produce an interaction specification with a single input and output
     * structure.
     * <p/>
     * Besides the target mainframe program characteristics, the interaction
     * spec also specifies the LegStar Transformers to be used for input anbd
     * output structures.
     * 
     * @param programName the target program name
     * @return the interaction spec
     */
    private LegStarSingleIOInteractionSpec getISpec(String programName) {
        LegStarSingleIOInteractionSpec ispec = new LegStarSingleIOInteractionSpecImpl();
        HostProgram hostProgram = new HostProgram();
        hostProgram.setName(programName);
        hostProgram.setMaxDataLength(79);
        ispec.setHostProgram(hostProgram);
        DfhcommareaTransformers tf = new DfhcommareaTransformers();
        ispec.setInputTransformer(tf.getJavaToHost());
        ispec.setInteractionId(programName + ":" + System.currentTimeMillis());
        ispec.setOutputTransformer(tf.getHostToJava());
        return ispec;

    }
}
